package cz.proks.easyhelpdesk.gae.api.impl.db;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import cz.proks.easyhelpdesk.api.ThreadMemberApi;
import cz.proks.easyhelpdesk.api.config.Configuration;
import cz.proks.easyhelpdesk.api.exception.ApiException;
import cz.proks.easyhelpdesk.api.model.HelpdeskRole;
import cz.proks.easyhelpdesk.api.model.ThreadMember;
import cz.proks.easyhelpdesk.gae.api.impl.dbo.HelpdeskMember;

@Repository(Configuration.DB.THREAD_MEMBER_API)
@Scope("prototype")
public class ThreadMemberApiImpl extends BaseApiImpl<ThreadMember> implements ThreadMemberApi {    
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    @Override
    protected Class<ThreadMember> getDomainClass() {
        return ThreadMember.class;
    }

    @Override
    protected ThreadMember getDboObject(ThreadMember entity) {
        cz.proks.easyhelpdesk.gae.api.impl.dbo.ThreadMember dbo = instantiateDbo(entity);

        if (dbo != null)
            dbo.toDbo(entity);

        return dbo;
    }
    
    @Override
    @Transactional(readOnly = true)
    public ThreadMember getForHelpdeskMemberAndThread(Long helpdeskMemberId, Long threadId) throws ApiException {
        log.debug("getForHelpdeskMemberAndThread():: Start with helpdeskMemberId '{}', threadId '{}'", helpdeskMemberId, threadId);
        
        ThreadMember threadMember = null;
        
        Query query = getEntityManager().createNamedQuery("ThreadMember.get");        
        query.setParameter("helpdeskMemberId", threadId);        
        query.setParameter("threadId", threadId);
        
        try {
            cz.proks.easyhelpdesk.gae.api.impl.dbo.ThreadMember dbo =  (cz.proks.easyhelpdesk.gae.api.impl.dbo.ThreadMember) query.getSingleResult();
            threadMember = fromDbo(dbo);
        } catch (EmptyResultDataAccessException erdae) {            
        }
                 
        log.debug("getForHelpdeskMemberAndThread():: Exit with entity '{}'", threadMember);
        return threadMember;
    }

    @Override
    @Transactional(readOnly = true)
    public List<ThreadMember> listForHelpdeskMember(Long helpdeskMemberId) throws ApiException {
        log.debug("listForHelpdeskMember():: Start with helpdeskMemberId '{}'", helpdeskMemberId);
        
        List<ThreadMember> entityList = null;
        
        Query query = getEntityManager().createNamedQuery("ThreadMember.listForHelpdeskMember");
        query.setParameter("helpdeskMemberId", helpdeskMemberId);        
        
        try {
            List<cz.proks.easyhelpdesk.gae.api.impl.dbo.ThreadMember> dboList = query.getResultList();
            entityList = fromDboList(dboList);
        } catch (EmptyResultDataAccessException erdae) {
            entityList = new LinkedList<ThreadMember>();
        }
                 
        log.debug("listForHelpdeskMember():: Exit with {} entities", entityList != null ? entityList.size() : null);
        return entityList;
    }
    
    @Override
    @Transactional(readOnly = false)
    public void setRead(Long id, boolean read) throws ApiException {
        log.debug("setRead():: Start with id '{}', read '{}'", id, read);
        
        ThreadMember threadMember = get(id);
        threadMember.setRead(read);
        
        update(threadMember);
        
        log.debug("setRead():: Exit");
    }

    @Override
    @Transactional(readOnly = false)
    public void shareForHelpdeskAndThreadAndRole(Long helpdeskId, Long threadId, HelpdeskRole helpdeskRole) throws ApiException {
        log.debug("shareForHelpdeskAndThreadAndRole():: Start with helpdeskId '{}', threadId '{}', helpdeskRole '{}'", new Object[] {helpdeskId, threadId, helpdeskRole});
        
        EntityManager entityManager = getEntityManager();
        
        // list other helpdeskmembers with role
        Query queryHM = entityManager.createNamedQuery("HelpdeskMember.listForHelpdesk");
        queryHM.setParameter("helpdeskId", helpdeskId);
        List<HelpdeskMember> dboHelpdeskMembers = queryHM.getResultList();
        
        listOnlyForRole(helpdeskRole, dboHelpdeskMembers);
             
        // list helpdeskmember - their threadmembers (threads)
        for (HelpdeskMember dboHelpdeskMember : dboHelpdeskMembers) {
            List<ThreadMember> threadMembers = listForHelpdeskMember(dboHelpdeskMember.getId());
            
            // if thread is not contained in threirs threads - create it
            if (!containsThread(threadId, threadMembers)) {
                ThreadMember threadMember = createThreadMember(dboHelpdeskMember.getId(), threadId);
                create(threadMember);
            }
        }            
        
        log.debug("shareForHelpdeskAndThreadAndRole():: Exit");  
    }

    @Override
    @Transactional(readOnly = false)
    public void shareForHelpdeskMemberAndRole(Long helpdeskMemberId, HelpdeskRole helpdeskRole) throws ApiException {        
        log.debug("shareForHelpdeskMemberAndRole():: Start with helpdeskMemberId '{}', helpdeskRole '{}'", helpdeskMemberId, helpdeskRole);
        
        EntityManager entityManager = getEntityManager();
        
        // get current helpdesk member
        HelpdeskMember currentHelpdeskMember = entityManager.find(HelpdeskMember.class, helpdeskMemberId);
        List<ThreadMember> currentThreadMembers = listForHelpdeskMember(helpdeskMemberId);
        
        // list other helpdesk members with role
        Query queryHM = entityManager.createNamedQuery("HelpdeskMember.listForHelpdesk");
        queryHM.setParameter("helpdeskId", currentHelpdeskMember.getHelpdeskId());
        List<HelpdeskMember> listedDboHelpdeskMembers = queryHM.getResultList();
        
        listOnlyForRole(helpdeskRole, listedDboHelpdeskMembers);
                   
        // list helpdeskmember - their threadmembers (threads)
        for (HelpdeskMember dboHelpdeskMember : listedDboHelpdeskMembers) {
            List<ThreadMember> listedThreadMembers = listForHelpdeskMember(dboHelpdeskMember.getId());
            
            if (listedThreadMembers != null)
                for (ThreadMember threadMember : listedThreadMembers) {
                    
                    // if listed thread is not contained in  currentThreadMembers - create it
                    if (!containsThread(threadMember.getThreadId(), currentThreadMembers)) {
                        ThreadMember newThreadMember = createThreadMember(currentHelpdeskMember.getId(), threadMember.getThreadId());                                                                       
                        create(newThreadMember);
                        currentThreadMembers.add(newThreadMember);
                    }
                } 
        }
        
        log.debug("shareForHelpdeskMemberAndRole():: Exit");
    }        
    
    private void listOnlyForRole(HelpdeskRole role, Collection<HelpdeskMember> dboMembers) {
        Iterator<HelpdeskMember> iterator = dboMembers.iterator();
        while (iterator.hasNext()) {
            HelpdeskMember helpdeskMember = (HelpdeskMember) iterator.next();
            if (!containsRole(role, helpdeskMember.getRoles()))
                iterator.remove();
        }
    }
    
    private boolean containsRole(HelpdeskRole role, Collection<cz.proks.easyhelpdesk.gae.api.impl.dbo.HelpdeskRole> dboRoles) {
        if (dboRoles == null)
            return false;
        
        for (cz.proks.easyhelpdesk.gae.api.impl.dbo.HelpdeskRole dboRole : dboRoles)
            if (dboRole.getRole() == role)
                return true;
            
        return false;        
    }
    
    private boolean containsThread(Long threadId, Collection<ThreadMember> threadMembers) {
        if (threadMembers == null)
            return false;
        
        for (ThreadMember threadMember : threadMembers)
            if (threadMember.getThreadId().equals(threadId))
                return true;
        
        return false;
    }
    
    private ThreadMember createThreadMember(Long helpdeskMemberId, Long threadId) {
        ThreadMember threadMember = new ThreadMember();
        
        threadMember.setHelpdeskMemberId(helpdeskMemberId);
        threadMember.setThreadId(threadId);
        threadMember.setRead(false);
        
        return threadMember;
    } 
}